package org.orbis.communicate;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
import java.util.SortedSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.logging.Level;
import org.msgpack.object.ArrayType;
import org.msgpack.rpc.Server;
import org.msgpack.rpc.error.RemoteError;
import org.msgpack.rpc.loop.EventLoop;
import org.orbis.communicate.console.Console;
import org.orbis.communicate.deleter.DeleterClient;
import org.orbis.communicate.deleter.DeleterHost;
import org.orbis.communicate.deleter.DeleterPackage;
import org.orbis.communicate.filter.SimpleFilter;
import org.orbis.communicate.hash.HashCompareTool;
import org.orbis.communicate.hash.Hashing;
import org.orbis.communicate.indexer.content.ContentIndexerClient;
import org.orbis.communicate.indexer.content.ContentIndexerHost;
import org.orbis.communicate.indexer.term.TermIndexerClient;
import org.orbis.communicate.indexer.term.TermIndexerHost;
import org.orbis.communicate.indexer.term.TermPackage;
import org.orbis.communicate.lock.IndexableSwitch;
import org.orbis.communicate.logging.TopLogger;
import org.orbis.communicate.node.Node;
import org.orbis.communicate.rpc.Communicator;
import org.orbis.communicate.rpc.PeerHandler;
import org.orbis.communicate.searcher.TermSearcherHost;
import org.orbis.constants.Constants;
import org.orbis.index.IndexingPackage;
import org.orbis.logging.LogParser;
import org.orbis.memorycontroller.struct.MemoryInfoMaterial;
import org.orbis.memorymanager.MemoryManager;
import org.orbis.memorymanager.TermInformation;
import org.orbis.search.SearchPackage;
import org.orbis.search.SearchResult;
import org.orbis.search.SearchResultPackage;
import org.orbis.structure.ContentInfo;
import org.orbis.structure.LookupPointer;
import org.orbis.term.Term;
import org.orbis.term.TermList;
import org.orbis.toolbox.ByteConverter;

public final class MainService{
	// MemoryManager -> Memory, Disk
	private static MainService instance;
	private MemoryManager memman;
	private Configuration config;

	private long uptime;

	private Server server;
	private Thread[] indexThreads;
	private Thread beaconThread;
	private ExecutorService execIndex, execSearch, execBeacon;
	private String firstHost;
	private int firstPort;
	private Communicator communicator;
	private SimpleFilter filter;
	private int indexCounter, searchCounter;
	private TopLogger topLogger;
	private IndexingPackage lastIndex;
	private SearchPackage lastSearch;
	private DeleterPackage lastDelete;
	private boolean isStartupFromBackup;

	private MainService(){}

	public static boolean instantize(String IPaddr, String rootDir) throws IOException, InterruptedException{
		boolean status = false;

		instance = new MainService();

		if(IPaddr != null) status = instance.getFromOtherNode(IPaddr, rootDir);
		else status = instance.getFromSetting(rootDir);
		if(!status) return false;

		instance.initialize();
		instance.setProperties();
		IndexableSwitch.unlock();

		instance.reliefTopLog();

		instance.uptime = System.currentTimeMillis();

		return true;
	}

	public static boolean instantizeTest() throws SecurityException, IOException{
		instance = new MainService();

		instance.config = Configuration.initWithTest();
		LogParser.setParser(Level.WARNING.getName(), instance.config.logDirectory());

		return true;
	}

	public void initializeLocal() throws IOException, InterruptedException{
		instance.initialize();
		instance.setProperties();
		IndexableSwitch.unlock();
	}

	private void reliefTopLog(){
		if(!topLogger.isBackupAvailable()) return;

		isStartupFromBackup = true;
		try{
			while(topLogger.isReliefAvailable()){
				int nextType = topLogger.nextType();
				if(nextType == TopLogger.TOPLOG_INDEX) postIndex(topLogger.reliefIndex());
				else if(nextType == (TopLogger.TOPLOG_DELETE | 0x10)) postDelete(topLogger.reliefDelete(0x10));
				else if(nextType == (TopLogger.TOPLOG_DELETE | 0x20)) postDelete(topLogger.reliefDelete(0x20));
				else if(nextType == (TopLogger.TOPLOG_DELETE | 0x40))
					postDelete(topLogger.reliefDelete(0x40));
			}
		}catch(IOException e){}

		topLogger.endRelief();
	}

	// Get global address
	private String getMyAddress() throws SocketException{
		Enumeration<NetworkInterface> enulfs = NetworkInterface.getNetworkInterfaces();
		if(!enulfs.hasMoreElements()) throw new NullPointerException();
		InetAddress addrInfo = null;
		String address = null;

		while(enulfs.hasMoreElements()){
			NetworkInterface ni = enulfs.nextElement();
			Enumeration<InetAddress> enuAddr = ni.getInetAddresses();

			addrInfo = enuAddr.nextElement();
			while(enuAddr.hasMoreElements()){
				addrInfo = enuAddr.nextElement();
				address = addrInfo.getHostAddress();
				System.out.println("[DEBUG] Address: " + address);
				// Accept only IPv4: Needs to replace
				if(!address.contains(":")){
					// IPv4
					// Kick local host
					if(address.equals("localhost")) continue;

					String[] digits = address.split("\\.");
					if(digits.length == 0) continue;
					System.out.println("[DEBUG] Digits: " + digits.length);

					int digitSecond = Integer.parseInt(digits[1]);

					// Kick local host
					if(digits[0].equals("10")) continue;
					// if(digits[0] == "172" && digitSecond >= 16 && digitSecond <= 31) continue;
					if(digits[0].equals("192") && digitSecond == 168) continue;
				}
			}
			if(!address.contains(":")) break;
		}
		if(address == null || address.contains(":")) return null;

		return address;
	}

	private void initialize() throws IOException, InterruptedException{
		// get my IPv4 address
		String address = getMyAddress();

		Node.property(config.virtualNodes(), config.beaconThreshold());
		Node myNode = Node.createLocal(address, config.peerport());
		// Add myself
		config.setMyNode(myNode);
		communicator = new Communicator();
		if(firstHost != null) getInformation();

		// Initial value, if user don't set content set
		server = new Server(EventLoop.defaultEventLoop());
		server.serve(new PeerHandler(this));
		server.listen(config.peerport());

		config.setIndexThreadNum(16);
		config.setSearchThreadNum(2);
		// Index threads
		execIndex = Executors.newFixedThreadPool(config.indexThreadNum());
		indexThreads = new Thread[config.indexThreadNum()];
		for(int i = 0; i < indexThreads.length; i++){
			if(i < 2) indexThreads[i] = new Thread(new ContentIndexerHost());
			else if(i == 2 || i == 3) indexThreads[i] =
				new Thread(new ContentIndexerClient(config.clientLog()));
			else if(i == 4 || i == 5) indexThreads[i] = new Thread(new TermIndexerHost());
			else if(i >= 6 && i <= 11) indexThreads[i] =
				new Thread(new TermIndexerClient(config.clientLog()));
			else if(i == 12 || i == 13) indexThreads[i] = new Thread(new DeleterHost());
			else if(i == 14 || i == 15) indexThreads[i] = new Thread(new DeleterClient());
			indexThreads[i].setDaemon(true);
			execIndex.execute(indexThreads[i]);
		}

		// Search threads
		execSearch = Executors.newFixedThreadPool(config.searchThreadNum());
		execBeacon = Executors.newFixedThreadPool(1);
		beaconThread =
			new Thread(new BeaconService(myNode, config.nodeList(), this, config.beaconInterval(), 1500 * 60,
				communicator));
		beaconThread.setDaemon(true);
		execBeacon.execute(beaconThread);

		filter = new SimpleFilter();
		// globally-use switch, initializes at here just once
		IndexableSwitch.initialize();

		int cluster = 0;
		if(firstHost != null){
			cluster = registerNode();
		}else refreshHashList();

		// Start up memory manager
		memman =
			new MemoryManager(config.blockSize(), config.blockNum(), config.workDirectory(),
				config.backupInterval(), config.replication(), config.getNodeAmount(), cluster,
				config.uniqueIndex(), this);

		indexCounter = searchCounter = 0;

		File logDir = new File(config.logDirectory());
		if(!logDir.exists()) logDir.mkdir();
		topLogger = new TopLogger(config.logDirectory() + "/top.log");

		lastIndex = null;
		lastSearch = null;
		lastDelete = null;

		synchronizeClock();
	}

	private boolean getFromSetting(String rootDir){
		File settingFile = new File(rootDir + "/webapps/orbis/orbis_setting.txt");
		if(!settingFile.exists()) settingFile = new File(rootDir + "../orbis_setting.txt");
		if(!settingFile.exists()) settingFile = new File(rootDir + "/orbis_setting.txt");
		if(!settingFile.exists()) return false;

		config = Configuration.initWithDefault();
		config.readSetting(settingFile);

		return true;
	}

	private boolean getFromOtherNode(String IPaddr, String rootDir){
		File settingFile = new File(rootDir + "/webapps/orbis/orbis_setting.txt");
		if(!settingFile.exists()) settingFile = new File(rootDir + "../orbis_setting.txt");
		if(!settingFile.exists()) settingFile = new File(rootDir + "/orbis_setting.txt");

		if(settingFile.exists()){
			config = Configuration.initWithDefault();
			config.readSetting(settingFile);
			String[] addrs = IPaddr.split(":");
			if(addrs.length < 2) return false;
			firstHost = addrs[0];
			firstPort = Integer.parseInt(addrs[1]);
		}

		return true;
	}

	public void terminate(){
		if(server != null) server.close();

		for(int i = 0; i < config.indexThreadNum(); i++){
			try{
				indexThreads[i].join(0);
			}catch(InterruptedException e){}
		}

		execSearch.shutdownNow();
		execIndex.shutdownNow();
		execBeacon.shutdownNow();

		// End logging
		LogParser.close();
		try{
			topLogger.closeFile();
			memman.terminate();
		}catch(IOException e){
			e.printStackTrace();
		}

		for(Node eachNode: config.nodeList()){
			try{
				if(!eachNode.equals(config.myNode()))
					communicator.finalizeProcess(eachNode, config.myNode());
			}catch(RemoteError ignored){}
		}
		config.setNodeList(null);
	}

	private boolean getArgs(String[] args){
		boolean isEnded = false;
		config = Configuration.initWithDefault();

		if(args.length < 2){
			System.err
				.println("Usage: MainService [-p(--primary) <SettingFile> | -s(--secondary) [-f <SettingFile>] <Address:port ...>]");
			return isEnded;
		}

		if(args.length > 1 && (args[0].equals("--primary") || args[0].equals("-p"))){
			File settingDesc = new File(args[1]);
			if(settingDesc.exists()) config.readSetting(new File(args[1]));
			else System.out.println("Setting file not found. Skip.");
			isEnded = true;
		}

		// Secondary mode
		if(args.length > 1 && (args[0].equals("--secondary") || args[0].equals("-s"))){
			int hostIndex = 1;
			// Find port file
			if(args[1].equals("-f")){
				hostIndex = 3;
				// Check whether setting file exists
				File settingDesc = new File(args[2]);
				if(settingDesc.exists()) config.readSetting(new File(args[2]));
				else System.out.println("Setting file not found. Skip.");
			}

			// IP address
			String[] hostSet = args[hostIndex].split(":");
			if(hostSet.length != 2) return isEnded;
			firstHost = hostSet[0];
			firstPort = Integer.parseInt(hostSet[1]);

			// logger
			try{
				LogParser.setParser(Level.WARNING.getName(), config.logDirectory());
			}catch(SecurityException e){}catch(IOException e){}

			isEnded = true;
		}

		return isEnded;
	}

	public static void main(String[] args) throws InterruptedException, IOException{
		MainService mainSystem = new MainService();
		if(!mainSystem.getArgs(args)) return;
		mainSystem.initialize();
		mainSystem.setProperties();
		IndexableSwitch.unlock();
		new Console(mainSystem).console();
	}

	private void setProperties(){
		TermIndexerHost.property(config.replication(), config);
		TermIndexerClient.property(memman);
		ContentIndexerHost.property(config.replication(), communicator, config);
		ContentIndexerClient.property(memman);
		DeleterHost.property(config.replication(), config.myNode(), config.nodeList());
		DeleterClient.property(memman);
		TermSearcherHost.property(config.replication(), memman, communicator, config);
		HashCompareTool.registerLists(config.replication(), config.myNode(), config.nodeList());
	}

	// Host indexing, make index ID to have ordered indexing
	public void postIndex(IndexingPackage newIndex){
		// System.out.println("[DEBUG] Index: " + newIndex.content(0) + " length: " +
		// newIndex.content(0).length());
		try{
			topLogger.addIndex(newIndex);
		}catch(IOException e){
			e.printStackTrace();
		}
		indexCounter++;

		// Calculate ID: ID will overflow in year 2109, 2248, ...
		/** This assigns 8-byte Internal ID to each content, which includes:
		 * <code>
		 * b63 20 19 10 9 0
		 * +----------------------------+-----------+-----------+
		 * | System.currentTimeMillis() |Microsecond|Node number|
		 * +----------------------------+-----------+-----------+
		 * </code> */
		long nowTime = System.currentTimeMillis();
		// Assume bits: nanosec.: 0-9 microsec.: 10-19 second: 20-25 ...
		long microsec = (System.nanoTime() & 0xFFC00) >> 10;
		nowTime = (nowTime << Constants.ID_SHIFT) + (microsec << Constants.SEQ_SHIFT) + getMyNodeIndex();
		newIndex.setID(nowTime);

		// Calculate destination
		newIndex.setCluster(config.getReplicateNodeIndex(nowTime));

		// Add queue
		ContentIndexerHost.addQueue(newIndex);

		for(int i = 0; i < newIndex.contentLength(); i++){
			long startTime = LogParser.initTime();
			TermList terms = config.chopper().chop(newIndex.content(i));
			// System.out.println("Term: " + (terms == null? 0: terms.size()));
			LogParser.timerFinest("Index / Chop", startTime);
			if(terms == null) continue;
			for(Term term: terms)
				TermIndexerHost.addToQueue(TermPackage.withFlag(term.str(), newIndex.ID(), term.freq(), i,
					newIndex));
		}
		lastIndex = newIndex;
	}

	public void postDelete(DeleterPackage pkg){
		try{
			topLogger.addDelete(pkg);
		}catch(IOException e){}
		DeleterHost.addToQueue(pkg);
		lastDelete = pkg;
	}

	// TODO: consider for multi thread searching
	// search process
	public SearchResultPackage postSearch(SearchPackage newQuery) throws Exception{
		lastSearch = newQuery;
		searchCounter++;
		long dbgtime = LogParser.initTime();
		TermList terms = config.chopper().chop(newQuery.query());
		SearchResultPackage structure = SearchResultPackage.make(terms, config.contentInfo(), newQuery, 10);
		dbgtime = LogParser.timerFinest("Search / Chop", dbgtime);

		// starts search on specified node
		for(int i = 0; i < structure.getCount(); i++)
			execSearch.execute(new TermSearcherHost(structure, i, newQuery.searchTypes()));
		dbgtime = LogParser.timerFinest("Search / Sending process", dbgtime);
		// intersection if all nodes finished search
		structure.waitLatch();
		LogParser.timerFinest("Search / Search terms", dbgtime);

		if(!structure.isNotFound()){
			try{
				dbgtime = LogParser.initTime();
				// intersection
				FlagsResult[] merged = null;
				if(terms.size() > 1){
					// result by multiple terms needs intersection
					if(newQuery.isAndSearch()) merged = filter.intersection(null, structure.getAllResult());
					else merged = filter.join(null, structure.getAllResult());
					dbgtime = LogParser.timerFinest("Search / Intersection", dbgtime);
					newQuery.setLength(merged);
					structure.setCount(merged.length);
				}else{
					// in the situation that has one term need not intersect, to get result faster
					merged = structure.getResult(0);
					newQuery.setLength(merged);
					structure.setCount(merged.length);
				}

				SearchResult result;
				for(int i = newQuery.from(); i < Math.min(merged.length, newQuery.from() + newQuery.length()); i++){
					result = getContent(merged[i].ID());
					if(result != null){
						result.setFlags(merged[i]);
						structure.setResult(result, i);
					}
				}
				dbgtime = LogParser.timerFinest("Search / Content", dbgtime);

				LogParser.timerFinest("Search / Get content", dbgtime);
			}catch(Exception e){
				e.printStackTrace();
			}
		}else{
			LogParser.fine("Not found.");
			newQuery.setLength(null);
		}

		return structure;
	}

	private SearchResult getContent(long ID){
		long hash = Hashing.getContentHash(ID);

		Entry<Long, Node> nearestEntry = config.ceilingHashEntry(hash);
		Node nearestNode = nearestEntry.getValue();
		int cluster = config.getNodeIndex(nearestNode);

		if(nearestNode.equals(config.myNode())){
			return memman.searchContent(ID, cluster);
		}else{
			for(int i = 0; i < config.replication(); i++){
				if(nearestNode.equals(config.myNode())) return memman.searchContent(ID, cluster);
				SearchResult result =
					communicator.getContentSet(ID, contentInfo().contentsNum(), contentInfo().appendixNum(),
						nearestNode, cluster);
				if(result != null) return result;
				// Find in another node
				nearestEntry = config.higherHashEntry(nearestEntry.getKey());
				nearestNode = nearestEntry.getValue();
				cluster = config.getNodeIndex(nearestNode);
			}
		}
		return null;
	}

	public void readMemory(){
		memman.initializeMemory();
	}

	public void saveMemory(){
		memman.saveMemory();
	}

	// ///////////////////// Implement for P2P service //////////////////////
	/** Invokes process to index content from communicator.
	 * 
	 * @param queue - Informations of content */
	public void invokeIndexContent(IndexingPackage queue){
		// run with less thread
		ContentIndexerClient.addToQueue(queue);
	}

	/** Invokes process to index terms from communicator.
	 * 
	 * @param str - term
	 * @param termfreq - term frequency in one content
	 * @param ID - content ID
	 * @param valueType - content's type */
	public void invokeIndexTerm(TermPackage pkg){
		// run with less thread
		TermIndexerClient.addToQueue(pkg);
	}

	/** Invokes process to search term.
	 * 
	 * @param query - search query
	 * @param valueType - content's type
	 * @return result for search in <code>ExtendTerm</code> */
	public TermInformation invokeSearch(String query, boolean[] valueType, int cluster){
		if(!IndexableSwitch.isIndexable()) try{
			IndexableSwitch.waitIfLocked();
		}catch(InterruptedException ignored){}
		return memman.searchTerm(query, valueType, cluster);
	}

	public FlagsResult[] invokeInvertIndex(LookupPointer ptr, SearchPackage pkg){
		return memman.getInvertIndex(ptr, pkg);
	}

	public void invokeDelete(DeleterPackage pkg, int cluster){
		pkg.setCluster(cluster);
		DeleterClient.addToQueue(pkg);
	}

	public LinkedHashMap<Long, Integer> getContentsByHash(LookupPointer ptr, int length){
		return null;
	}

	public ArrayType putInfo(){
		ArrayType info = config.pack();
		return info;
	}

	public int getQueueSize(int type){
		if(type == 0) return ((ThreadPoolExecutor)execIndex).getPoolSize();
		else if(type == 1) return ((ThreadPoolExecutor)execSearch).getPoolSize();
		else return -1;
	}

	public SearchResult lookupContent(long ID, int cluster){
		return memman.searchContent(ID, cluster);
	}

	// Get an information of host.
	private boolean getInformation(){
		ArrayType infos = communicator.getInfo(firstHost, firstPort);
		if(infos == null) return false;
		config.setNodeInfo(infos);
		return true;
	}

	// Add a node.
	public void addNode(Node newNode){
		int suspendIndex = config.getNodeIndex(newNode);
		if(suspendIndex == -1){
			// add to chain
			config.addToNodeList(newNode);
			// modify ciuster information and block number
			memman.addCluster(config.getNodeIndex(config.myNode()), config.getNodeAmount());
			refreshHashList();
		}else{
			Node nodeInList = config.getNode(suspendIndex);
			resume(nodeInList);
		}
	}

	// Registers node.
	private int registerNode(){
		// Order to get node list
		try{
			for(Node eachNode: communicator.getNodeList(firstHost, firstPort))
				config.addToNodeList(eachNode);
		}catch(Exception e){
			LogParser.warning("Node might be not found, retry in next node.");
			return -1;
		}
		// Send own node information use with node list
		ArrayList<Node> notConnected = new ArrayList<Node>();
		boolean result = false;
		for(Node eachNode: config.nodeList()){
			if(eachNode == config.myNode()) continue;
			try{
				result = communicator.register(eachNode, config.myNode());
			}catch(RemoteError e){
				e.printStackTrace();
				System.out.printf("Error while call %s:%s%n", eachNode.getAddr(), eachNode.getPort());
			}
			if(!result) notConnected.add(eachNode);
		}
		// Remove from list that cannot connect
		// Add unconnected node information to suspend node
		for(Node deleted: notConnected){
			// config.removeNode(deleted);
			suspend(deleted, 1);
		}

		// synchronize timings with other nodes
		for(Node eachNode: config.nodeList()){
			if(eachNode == config.myNode()) continue;
			communicator.synchronizeClock(eachNode);
		}
		// add myself
		refreshHashList();

		return config.getNodeIndex(config.myNode());
	}

	public void copyItems(int clusterID, Node fromNode){
		communicator.getDatas(clusterID, fromNode, config.myNode(), this);
	}

	// hashList needs to synchronize
	private void refreshHashList(){
		// if(config.isFullCopy()){
		for(int i = 0; i < config.getNodeAmount(); i++){
			Node currentNode = config.getNode(i);
			config.addToHashList(currentNode.getAttributeHash(Long.MIN_VALUE), currentNode);
		}
		return;
		// }

		// Get whole hash table
		// for(int i = 0; i < config.getNodeAmount(); i++){
		// Node currentNode = config.getNode(i);
		// for(Long eachHash: currentNode)
		// config.addToHashList(eachHash, currentNode);
		// }
		//
		// Entry<Long, Node> currentEntry = config.firstHashEntry();
		// Entry<Long, Node> nextEntry = config.nextHashEntry(currentEntry.getKey());
		// // Delete the same node in a row
		// for(; nextEntry != null;){
		// // Every entry can compare in pointers
		// if(currentEntry.getValue().getAddr().equals(nextEntry.getValue().getAddr()))
		// config.removeHashList(currentEntry.getKey());
		// // FIXME: High cost!
		// currentEntry = config.nextHashEntry(currentEntry.getKey());
		// nextEntry = config.nextHashEntry(nextEntry.getKey());
		// }
		// if(config.hashListSize() != 1
		// && config.firstHashEntry().getValue() == config.lastHashEntry().getValue())
		// config.removeHashList(config.lastHashEntry().getKey());
	}

	// It works for deletion.
	@Deprecated
	public void deleteReplication(String IPaddr, int port){
		Node deletedNode = Node.createTemp(IPaddr, port);
		// Node deletedInList = settings.getSameNode(deletedNode);
		// int deletedIndex = settings.getNodeIndex(deletedInList);

		// delete from chain
		config.removeNode(deletedNode);
		refreshHashList();
	}

	public void showNodes(){
		int count = 0;
		System.out.printf("Total node: %d, Issued hash per node: %d%n", config.getNodeAmount(),
			config.virtualNodes());
		for(Node eachNode: config.nodeList()){
			System.out.printf("%3d: Address-%s:%d client-%s", (++count), eachNode.getAddr(),
				eachNode.getPort(), (eachNode.client() != null? "connect   ": "disconnect"));
			System.out.printf(" 0x%016x", eachNode.getAttributeHash(0));
			if(eachNode.equals(config.myNode())) System.out.printf(" [Running] Indexable-%s%n",
				(IndexableSwitch.isIndexable()? "Yes": "No"));
			else if(eachNode.isSuspend()) System.out.printf(" [Suspend] Left: %d sec%n", eachNode.timeLeft());
			else System.out.printf(" [Running]%n");
		}
	}

	public void showProcesses(){
		System.out.printf("Thread - Indexer/Deleter: %d, Searcher: %d%n", config.indexThreadNum(),
			config.searchThreadNum());
		System.out.println("Queues:");
		System.out.printf("Term:     Host %5d / Client %5d%n", TermIndexerHost.residue(),
			TermIndexerClient.residue());
		System.out.printf("Contents: Host %5d / Client %5d%n", ContentIndexerHost.residue(),
			ContentIndexerClient.residue());
		System.out
			.printf("Delete:   Host %5d / Client %5d%n", DeleterHost.residue(), DeleterClient.residue());
	}

	public SortedSet<Node> getNode(){
		return config.nodeList();
	}

	public ContentInfo contentInfo(){
		return config.contentInfo();
	}

	public int getIndexCounter(){
		return indexCounter;
	}

	public int getSearchCounter(){
		return searchCounter;
	}

	public int getDeleteCounter(){
		return memman.getDeleteCount();
	}

	public int getTerms(){
		return memman.getTerms();
	}

	public int getSkipPointerLen(){
		return memman.getSkipPointerLen(0);
	}

	public ArrayList<MemoryInfoMaterial> getStatusList(){
		return memman.getStatusList();
	}

	public void getMemoryList(){
		memman.getMemoryList();
	}

	public void optimizeMemory(){
		memman.optimizeMemory();
	}

	public int getClusterBlockAmount(int clusterID){
		return memman.getClusterBlockAmount(clusterID);
	}

	// Synchronize timings to emit beacon and backup
	public void synchronizeClock(){
		LogParser.fine("Synchronize");
		beaconThread.interrupt();
	}

	// Suspend specified node
	public void suspend(String IPaddr, int port, long suspendTime){
		Node tempNode = Node.createTemp(IPaddr, port);
		Node nodeFromList = config.getSameNode(tempNode);
		nodeFromList.suspend(suspendTime);
	}

	// Suspend specified node
	public void suspend(Node node, long suspendTime){
		node.suspend(suspendTime);
	}

	private void issueSuspendInfo(){
		File infoFile = new File(config.workDirectory() + ".suspendInfo");
		if(infoFile.exists()) infoFile.delete();
		try{
			infoFile.createNewFile();
		}catch(IOException e){
			System.err.println("Suspend file not issued due to interval error.");
			LogParser.fine("Error: While creating file \".suspendInfo\", IOException");
		}

		FileOutputStream stream = null;
		try{
			stream = new FileOutputStream(infoFile);
			stream.write(ByteConverter.longToByte(System.currentTimeMillis(), 8));
			stream.write(ByteConverter.longToByte(20 * 60, 8));
			// stream.write(ByteConverter.longToByte(memman.latestBackupIndex(), 8));
		}catch(FileNotFoundException e){}catch(IOException e){}finally{
			if(stream != null) try{
				stream.close();
			}catch(IOException e){}
		}
	}

	// Output backup index
	private long getSuspendInfo(){
		File infoFile = new File(".suspendInfo");
		if(!infoFile.exists()) return -1;
		byte[] readbyte = new byte[8];
		long suspendTime, suspendLength;
		long ID = -1;
		FileInputStream stream = null;
		try{
			stream = new FileInputStream(infoFile);
			stream.read(readbyte);
			suspendTime = ByteConverter.byteToLong(readbyte);
			stream.read(readbyte);
			suspendLength = ByteConverter.byteToLong(readbyte);
			if(suspendTime + (suspendLength * 1000) >= System.currentTimeMillis()){
				stream.read(readbyte);
				ID = ByteConverter.byteToLong(readbyte);
			}
		}catch(FileNotFoundException e){}catch(IOException e){}finally{
			if(stream != null) try{
				stream.close();
			}catch(IOException e){}
		}
		return ID;
	}

	// Suspend my node
	public void suspendMyNode(long suspendTime){
		// Backup my datas
		// memman.backupLatest();
		IndexableSwitch.lock();
		issueSuspendInfo();
		for(Node eachNode: config.nodeList()){
			if(eachNode != config.myNode())
				communicator.suspend(eachNode, config.myNode(), suspendTime, "Suspend");
		}
	}

	public void resumeMyNode(){
		getSuspendInfo();
		for(Node eachNode: config.nodeList()){
			if(eachNode != config.myNode()) communicator.resume(eachNode, config.myNode());
		}
		try{
			IndexableSwitch.unlock();
		}catch(InterruptedException e){}
	}

	public void resume(String IPaddr, int port){
		Node tempNode = Node.createTemp(IPaddr, port);
		resume(tempNode);
	}

	public void resume(Node node){
		Node nodeFromList = config.getSameNode(node);
		if(nodeFromList == null) return;
		nodeFromList.resume();
		TermIndexerHost.resumeQueue(nodeFromList);
		ContentIndexerHost.resumeQueue(nodeFromList);
	}

	public int getMyNodeIndex(){
		return config.getNodeIndex(config.myNode());
	}

	public void setClusterPointInfo(ArrayType info){
		memman.setClusterPointInfo(info);
	}

	public ArrayType getClusterPointInfo(int clusterID){
		return memman.getClusterPointInfo(clusterID);
	}

	public ArrayType toArrayType(int cluster, int block){
		return memman.toArrayType(cluster, block);
	}

	public void fromArrayType(int cluster, int block, int type, int subtype, byte[] array){
		memman.fromArrayType(cluster, block, type, subtype, array);
	}

	public Configuration getConf(){
		return config;
	}

	public static MainService getInstance(){
		return instance;
	}

	public long startSec(){
		return uptime;
	}

	public IndexingPackage lastIndex(){
		return lastIndex;
	}

	public SearchPackage lastSearch(){
		return lastSearch;
	}

	public DeleterPackage lastDelete(){
		return lastDelete;
	}

	public boolean isStartupFromBackup(){
		return isStartupFromBackup;
	}
}
